import { getObjValue } from "../utils/index";
import { popTarget, pushTarget } from "./dep";
import { queueWatcher } from "./schedular";


let wid = 0;
class Watcher {
  constructor(vm, exprOrFn, cb, options) {
    this.id = wid++;
    this.vm = vm;
    this.cb = cb; // vm.$watch 等的一些实例方法
    this.options = options; // 其他一些内部参数

    this.lazy = !!options.lazy; // 表示用户默认是否调用 computed 对应的属性函数
    this.dirty = this.lazy; // 对计算属性做缓存

    this.user = !!options.user; // 标识是用户的 watcher
    if (typeof exprOrFn === 'function') {
      this.getter = exprOrFn; // 更新渲染函数(updateComponent)，将内部传过来的回调函数 放到getter属性上
    } else {
      // $watch 或者 watch 或来这里
      // console.log('exprOrFn', exprOrFn)
      this.getter = function () {
        // return vm[exprOrFn]; // 返回实例上的 watch 的属性值
        console.log(getObjValue(vm, 'vm.' + exprOrFn))
        return getObjValue(vm, 'vm.' + exprOrFn);
      }
    }

    this.depsId = new Set(); // 存放 dep 的 id
    this.deps = []; // 存放 dep

    this.value = this.lazy ? void (0) : this.get(); // 实现页面的渲染
  }

  get() {
    /*
   执行页面更新，就是执行方法 updateComponent, 本质还是调用 render 函数
   const updateComponent = () => {
     const vnode = vm._render(); // 调用 render 方法返回的虚拟 DOM
     vm._update(vnode); // 将虚拟 DOM 转换成真实 DOM
   }
   */

    // console.log('get')
    // 依赖收集
    pushTarget(this); // 存放 Watcher, 即挂载到 Dep.target 上
    const value = this.getter.call(this.vm); // 执行更新渲染函数（updateComponent），本质是 render 函数
    popTarget(); // 删除 Watcher
    return value;
  }

  /**
   * 存储对应的 Dep
   * @param {*} dep Dep 实例
   */
  addDep(dep) {
    const id = dep.id;
    // Watcher 中不能有重复的 dep，反之 dep 中也不能有重复的 Watcher
    if (!this.depsId.has(id)) {
      this.depsId.add(id);
      this.deps.push(dep);
      dep.addWatcher(this); // 调用 Dep 的 addWatcher 函数，添加对应的渲染 Watcher， 让 Dep 记住当前的 Watcher
    }
  }

  // 调用视图更新函数（updateComponent），本质是 render 函数
  update() {
    // console.log(this.id)
    // this.get(); // 这种方式会多次调用 该方法 update，对同一个 id 的 Watcher 有多次调用视图更新，不友好

    /**
      解决调用 this.get() 直接更新导致多次渲染的问题, 异步更新，等数据都处理好之后再对视图更新
      vm.arr.push(1111);
      vm.arr.push(2222);
      vm.arr.push(3333);
      或者
      vm.name = "1111";
      vm.name = "2222";
      vm.name = "3333";
    */
    if (this.lazy) { // 计算属性
      this.dirty = true; // 计算属性依赖的属性值变化了就设置 dirty 为 true，触发更新属性
      // 更新属性后，还需要更新页面视图

    } else { // 渲染 watcher 和 用户 watcher
      queueWatcher(this);
    }
  }

  run() {
    // console.log(this.id);
    // 调用更新渲染函数（updateComponent），本质是 render 函数
    const newValue = this.get();
    const oldValue = this.value;
    this.value = newValue;
    if (this.user) {
      this.cb.call(this.vm, newValue, oldValue); // 用户 watcher
    }
  }

  // 计算属性中会调用
  evaluate() {
    // console.log("evaluate");
    this.value = this.get();
    this.dirty = false; // 缓存，防止多次取值都会执行
  }
  // 计算属性中会调用
  depend() {
    let i = this.deps.length;
    while (i--) {
      this.deps[i].depend(); // 让当前的计算属性的 dep 记住 watcher，这样计算属性的中属性值变化后，就更新页面视图
    }
  }
}

/**
 * 让每个属性自己对应的渲染函数，如果属性发生变化就调用对应的渲染函数去更新视图
 * 一个 watcher 对应多个 dep，一个属性对应多个 watcher
 */

export default Watcher;